Skip to content

feat(portfolio): add deduct_management_fee for flat annual fee deduction - #901

Merged
tschm merged 5 commits into
mainfrom
copilot/add-management-fee-deduction-utility
Jul 29, 2026
Merged

feat(portfolio): add deduct_management_fee for flat annual fee deduction#901
tschm merged 5 commits into
mainfrom
copilot/add-management-fee-deduction-utility

Conversation

Copilot AI commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds Portfolio.deduct_management_fee to subtract a flat annual management fee from a returns series, accruing pro-rata per calendar day (annual_fee × days_elapsed / 365). Weekends and holidays are charged to the next trading day; the first row always accrues nothing.

Changes

  • exceptions.py — new NegativeAnnualFeeError (mirrors NegativeCostBpsError)
  • _portfolio_base.py — adds annual_fee: float to _PortfolioMembers TYPE_CHECKING stubs
  • portfolio.py — adds annual_fee: float = 0.0 dataclass field; updates cost-model docstring section; updates from_cash_position to accept and forward annual_fee
  • _portfolio_constructors.pyfrom_position and from_risk_position accept and forward annual_fee
  • _portfolio_cost.py — implements deduct_management_fee(annual_fee, base):
    • Uses actual calendar-day diffs when a date column is present; falls back to 1 day/period otherwise
    • base defaults to self.returns; accepts any same-schema DataFrame so it composes with cost_adjusted_returns
    • Full input validation (type, finite, non-negative)
  • test_costs.py — 16 new tests (zero-fee identity, per-period analytical values, full-year sum, weekend accumulation, composition, validation errors, factory forwarding)
# Set at construction
pf = Portfolio.from_cash_position(prices=prices, cash_position=pos, aum=1e6, annual_fee=0.0085)
net = pf.deduct_management_fee()

# Or compose with trading costs
adj = pf.cost_adjusted_returns(cost_bps=5)
net = pf.deduct_management_fee(annual_fee=0.0085, base=adj)

Testing

  • make test passes locally
  • make fmt has been run
  • New tests added (or explain why not needed)

Checklist

  • Commit messages follow the Conventional Commits format
  • CHANGELOG.md entry added (or not needed for this change)
  • Documentation updated if behaviour changed
  • make deptry passes (no unused or missing dependencies)

Copilot AI review requested due to automatic review settings July 29, 2026 09:23

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because there is no eligible user to bill. To allow Copilot reviews on bot-authored pull requests, enable direct organization billing in your organization's Copilot settings.

Copilot AI linked an issue Jul 29, 2026 that may be closed by this pull request
Copilot AI review requested due to automatic review settings July 29, 2026 09:32

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because there is no eligible user to bill. To allow Copilot reviews on bot-authored pull requests, enable direct organization billing in your organization's Copilot settings.

Comment thread tests/test_jquantstats/test_portfolio/test_costs.py Fixed
Comment thread tests/test_jquantstats/test_portfolio/test_costs.py Fixed
Comment thread tests/test_jquantstats/test_portfolio/test_costs.py Fixed
Comment thread tests/test_jquantstats/test_portfolio/test_costs.py Fixed
Copilot AI review requested due to automatic review settings July 29, 2026 09:33

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because there is no eligible user to bill. To allow Copilot reviews on bot-authored pull requests, enable direct organization billing in your organization's Copilot settings.

Copilot AI review requested due to automatic review settings July 29, 2026 09:35

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because there is no eligible user to bill. To allow Copilot reviews on bot-authored pull requests, enable direct organization billing in your organization's Copilot settings.

Copilot AI changed the title [WIP] Add management fee deduction utility feat(portfolio): add deduct_management_fee for flat annual fee deduction Jul 29, 2026
Copilot finished work on behalf of tschm July 29, 2026 09:36
Copilot AI requested a review from tschm July 29, 2026 09:36
@tschm
tschm marked this pull request as ready for review July 29, 2026 12:24
Replace the ambiguous en dash and arrow in a comment with ASCII (RUF003)
and let ruff format normalise the inline weekday comments.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 29, 2026 15:32

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (1)

src/jquantstats/_portfolio_cost.py:213

  • deduct_management_fee accepts an arbitrary base frame but doesn't validate it has a 'returns' column and builds the undated days_elapsed via a Python list sized from base.height. This can fail with confusing errors (e.g., ColumnNotFound for missing 'returns') and will produce a length mismatch when base.height == 0 (creating a 1-element Series for a 0-row frame). Adding lightweight validation and generating the undated days_elapsed with pl.arange(...).clip(...) avoids both issues and keeps the operation in Polars.
        if base is None:
            base = self.returns
        if "date" in base.columns and base["date"].dtype.is_temporal():

@tschm
tschm merged commit 29dcb19 into main Jul 29, 2026
64 checks passed
@tschm
tschm deleted the copilot/add-management-fee-deduction-utility branch July 29, 2026 15:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add management fee deduction utility

3 participants